/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Graphics.PackedVector;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;

namespace Cobalt.Content.Pipeline.Quake.Bsp
{
	partial class BspImporter
	{
		#region LoadBspData
		private const int NumLumps = 17;

		private const int BspIdent = ('P' << 24) | ('S' << 16) | ('B' << 8) | 'I';
		private const int BspVersion = 46;

		private struct LumpDesc
		{
			public int Offset, Length;
		}

		private ContentIdentity identity;
		private LumpDesc[] header;
		private byte[] bspData;

		private void LoadBspData( string filename )
		{
			MemoryStream memBytes;

			string fullPath;
			using( Stream dataStream = fs.OpenPath( filename, out fullPath ) )
			{
				memBytes = new MemoryStream( (int)dataStream.Length );
				Helpers.CopyStream( memBytes, dataStream );
				memBytes.Seek( 0, SeekOrigin.Begin );
			}

			this.identity = new ContentIdentity( fullPath );
			LumpDesc[] header = new LumpDesc[NumLumps];

			using( BinaryReader reader = new BinaryReader( memBytes ) )
			{
				int headIdent = reader.ReadInt32();
				int headVer = reader.ReadInt32();

				if( headIdent != BspIdent || headVer != BspVersion )
					throw new InvalidContentException( "Invalid BSP header.", identity );

				for( int i = 0; i < header.Length; i++ )
				{
					header[i].Offset = reader.ReadInt32();
					header[i].Length = reader.ReadInt32();
				}
			}

			this.bspData = memBytes.GetBuffer();
			this.header = header;
		}

		private void UnloadBspData()
		{
			bspData = null;
			header = null;
			identity = null;
		}
		#endregion

		#region LoadLump
		protected enum Lumps
		{
			Entities = 0,
			Shaders = 1,
			Planes = 2,
			Nodes = 3,
			Leafs = 4,
			LeafSurfaces = 5,
			LeafBrushes = 6,
			Models = 7,
			Brushes = 8,
			BrushSides = 9,
			DrawVerts = 10,
			DrawIndices = 11,
			Fogs = 12,
			Surfaces = 13,
			Lightmaps = 14,
			LightGrid = 15,
			Visibility = 16,
		}

		protected Stream LoadLump( Lumps lump )
		{
			if( (int)lump < 0 || (int)lump > header.Length )
				throw new ArgumentOutOfRangeException( "lump" );

			LumpDesc desc = header[(int)lump];
			return new MemoryStream( bspData, desc.Offset, desc.Length, false, false );
		}
		#endregion

		#region Helpers
		protected static string ReadQuakeString( BinaryReader reader, int charCount )
		{
			byte[] text = reader.ReadBytes( charCount );

			int c;
			for( c = 0; c < text.Length; c++ )
			{
				if( text[c] == 0 )
					break;
			}

			return Encoding.ASCII.GetString( text, 0, c );
		}

		protected static Vector2 ReadVector2( BinaryReader reader )
		{
			Vector2 ret;
			ret.X = reader.ReadSingle();
			ret.Y = reader.ReadSingle();
			return ret;
		}

		protected static Vector3 ReadVector3( BinaryReader reader )
		{
			Vector3 ret;
			ret.X = reader.ReadSingle();
			ret.Y = reader.ReadSingle();
			ret.Z = reader.ReadSingle();
			return ret;
		}

		protected static Color ReadColor( BinaryReader reader )
		{
			byte r = reader.ReadByte();
			byte g = reader.ReadByte();
			byte b = reader.ReadByte();
			byte a = reader.ReadByte();

			return new Color( r, g, b, a );
		}

		protected delegate T Parser<T>( BinaryReader reader );
		protected static IEnumerable<T> ParseLump<T>( Stream lumpData,
			Parser<T> parser, int elementSize )
		{
			using( BinaryReader reader = new BinaryReader( lumpData ) )
			{
				for( int i = 0, n = (int)(lumpData.Length / elementSize); i < n; i++ )
					yield return parser( reader );
			}
		}

		protected IEnumerable<T> ParseLump<T>( Lumps lump,
			Parser<T> parser, int elementSize )
		{
			return ParseLump( LoadLump( lump ), parser, elementSize );
		}
		#endregion

		#region EntityString
		protected string LoadEntityString()
		{
			LumpDesc desc = header[(int)Lumps.Entities];
			int len = desc.Length;
			if( bspData[desc.Offset + len - 1] == 0 )
				len--;
			return Encoding.ASCII.GetString( bspData, desc.Offset, len );
		}
		#endregion

		#region DShader
		protected struct DShader
		{
			public string ShaderName;
			public int SurfaceFlags;
			public int ContentFlags;

			internal const int Size = 72;

			internal static DShader Load( BinaryReader reader )
			{
				DShader ret = new DShader();

				ret.ShaderName = ReadQuakeString( reader, 64 );
				ret.SurfaceFlags = reader.ReadInt32();
				ret.ContentFlags = reader.ReadInt32();

				return ret;
			}
		}

		protected IEnumerable<DShader> LoadDShaders()
		{
			return ParseLump<DShader>( Lumps.Shaders, DShader.Load, DShader.Size );
		}
		#endregion

		#region Lightmaps
		private const int LightmapSize = 128;
		private const int LightmapTexels = LightmapSize * LightmapSize;
		private const int LightmapByteCount = LightmapTexels * 3;

		private static PixelBitmapContent<Color> LoadLightmap( BinaryReader reader )
		{
			byte[] bits = new byte[LightmapTexels * 4];
			reader.BaseStream.Read( bits, 0, LightmapByteCount );

			for( int i = LightmapTexels - 1; i >= 0; i-- )
			{
				byte r = bits[i * 3 + 0];
				byte g = bits[i * 3 + 1];
				byte b = bits[i * 3 + 2];

				bits[i * 4 + 0] = b;
				bits[i * 4 + 1] = g;
				bits[i * 4 + 2] = r;
				bits[i * 4 + 3] = 0xFF;
			}

			PixelBitmapContent<Color> ret = new PixelBitmapContent<Color>( LightmapSize, LightmapSize );
			ret.SetPixelData( bits );
			return ret;
		}

		protected IEnumerable<PixelBitmapContent<Color>> LoadLightmaps()
		{
			return ParseLump<PixelBitmapContent<Color>>( Lumps.Lightmaps, LoadLightmap, LightmapByteCount );
		}
		#endregion

		#region DSurface
		public enum SurfaceType : int
		{
			Invalid,
			Planar,
			Patch,
			TriangleSoup,
			Flare,
		}

		protected struct DSurface
		{
			public int ShaderIndex;
			public int FogNum;

			public SurfaceType SurfaceType;

			public int FirstVertex;
			public int VertexCount;

			public int FirstIndex;
			public int IndexCount;

			public int LightmapIndex;

			public int LightmapX, LightmapY, LightmapW, LightmapH;

			public Vector3 LightmapOrigin;

			public Vector3 PatchBound0;
			public Vector3 PatchBound1;
			public Vector3 SurfaceNormal;

			public int PatchWidth;
			public int PatchHeight;

			internal const int Size = 104;

			internal static DSurface Load( BinaryReader reader )
			{
				DSurface ret = new DSurface();

				ret.ShaderIndex = reader.ReadInt32();
				ret.FogNum = reader.ReadInt32();

				ret.SurfaceType = (SurfaceType)reader.ReadInt32();

				ret.FirstVertex = reader.ReadInt32();
				ret.VertexCount = reader.ReadInt32();

				ret.FirstIndex = reader.ReadInt32();
				ret.IndexCount = reader.ReadInt32();

				ret.LightmapIndex = reader.ReadInt32();

				ret.LightmapX = reader.ReadInt32();
				ret.LightmapY = reader.ReadInt32();
				ret.LightmapW = reader.ReadInt32();
				ret.LightmapH = reader.ReadInt32();

				ret.LightmapOrigin = ReadVector3( reader );

				ret.PatchBound0 = ReadVector3( reader );
				ret.PatchBound1 = ReadVector3( reader );
				ret.SurfaceNormal = ReadVector3( reader );

				ret.PatchWidth = reader.ReadInt32();
				ret.PatchHeight = reader.ReadInt32();

				return ret;
			}
		}

		protected IEnumerable<DSurface> LoadDSurfaces()
		{
			return ParseLump<DSurface>( Lumps.Surfaces, DSurface.Load, DSurface.Size );
		}
		#endregion

		#region DrawVert
		protected internal struct DrawVert
		{
			public Vector3 Position;
			public Vector2 TextureCoord;
			public Vector2 LightmapCoord;
			public Vector3 Normal;
			public Color Color;

			internal const int Size = 44;

			internal static DrawVert Load( BinaryReader reader )
			{
				DrawVert ret;

				ret.Position = ReadVector3( reader );
				ret.TextureCoord = ReadVector2( reader );
				ret.LightmapCoord = ReadVector2( reader );
				ret.Normal = ReadVector3( reader );
				ret.Color = ReadColor( reader );

				return ret;
			}
		}

		protected IEnumerable<DrawVert> LoadDrawVerts()
		{
			return ParseLump<DrawVert>( Lumps.DrawVerts, DrawVert.Load, DrawVert.Size );
		}
		#endregion

		#region DrawIndices
		protected IEnumerable<int> LoadDrawIndices()
		{
			return ParseLump<int>( Lumps.DrawIndices, delegate( BinaryReader reader ) { return reader.ReadInt32(); }, 4 );
		}
		#endregion

		#region DModel
		protected struct DModel
		{
			public Vector3 Mins, Maxs;
			public int FirstSurface, NumSurfaces;
			public int FirstBrush, NumBrushes;

			internal const int Size = 40;
			internal static DModel Load( BinaryReader reader )
			{
				DModel ret;

				ret.Mins = ReadVector3( reader );
				ret.Maxs = ReadVector3( reader );
				ret.FirstSurface = reader.ReadInt32();
				ret.NumSurfaces = reader.ReadInt32();
				ret.FirstBrush = reader.ReadInt32();
				ret.NumBrushes = reader.ReadInt32();

				return ret;
			}
		}

		protected IEnumerable<DModel> LoadDModels()
		{
			return ParseLump<DModel>( Lumps.Models, DModel.Load, DModel.Size );
		}
		#endregion

		#region DPlane
		protected struct DPlane
		{
			public Vector3 Normal;
			public float Distance;

			internal const int Size = 16;
			internal static DPlane Load( BinaryReader reader )
			{
				DPlane ret;
				ret.Normal = ReadVector3( reader );
				ret.Distance = reader.ReadSingle();
				return ret;
			}
		}

		protected IEnumerable<DPlane> LoadDPlanes()
		{
			return ParseLump<DPlane>( Lumps.Planes, DPlane.Load, DPlane.Size );
		}
		#endregion

		#region DNode
		protected struct DNode
		{
			public int PlaneNumber;
			public int Child0, Child1;
			public Vector3 Mins;
			public Vector3 Maxs;

			internal const int Size = 36;
			internal static DNode Load( BinaryReader reader )
			{
				DNode ret;

				ret.PlaneNumber = reader.ReadInt32();
				ret.Child0 = reader.ReadInt32();
				ret.Child1 = reader.ReadInt32();

				ret.Mins.X = (float)reader.ReadInt32();
				ret.Mins.Y = (float)reader.ReadInt32();
				ret.Mins.Z = (float)reader.ReadInt32();

				ret.Maxs.X = (float)reader.ReadInt32();
				ret.Maxs.Y = (float)reader.ReadInt32();
				ret.Maxs.Z = (float)reader.ReadInt32();

				return ret;
			}
		}

		protected IEnumerable<DNode> LoadDNodes()
		{
			return ParseLump<DNode>( Lumps.Nodes, DNode.Load, DNode.Size );
		}
		#endregion

		#region DLeaf
		protected struct DLeaf
		{
			public int Cluster;
			public int Area;

			public Vector3 Mins;
			public Vector3 Maxs;

			public int FirstLeafSurface;
			public int NumLeafSurfaces;

			public int FirstLeafBrush;
			public int NumLeafBrushes;

			internal const int Size = 48;

			internal static DLeaf Load( BinaryReader reader )
			{
				DLeaf ret;

				ret.Cluster = reader.ReadInt32();
				ret.Area = reader.ReadInt32();

				ret.Mins.X = (float)reader.ReadInt32();
				ret.Mins.Y = (float)reader.ReadInt32();
				ret.Mins.Z = (float)reader.ReadInt32();

				ret.Maxs.X = (float)reader.ReadInt32();
				ret.Maxs.Y = (float)reader.ReadInt32();
				ret.Maxs.Z = (float)reader.ReadInt32();

				ret.FirstLeafSurface = reader.ReadInt32();
				ret.NumLeafSurfaces = reader.ReadInt32();

				ret.FirstLeafBrush = reader.ReadInt32();
				ret.NumLeafBrushes = reader.ReadInt32();

				return ret;
			}
		}

		protected IEnumerable<DLeaf> LoadDLeafs()
		{
			return ParseLump<DLeaf>( Lumps.Leafs, DLeaf.Load, DLeaf.Size );
		}
		#endregion

		#region LeafBrushes
		protected IEnumerable<int> LoadLeafBrushes()
		{
			return ParseLump<int>( Lumps.LeafBrushes, delegate( BinaryReader reader ) { return reader.ReadInt32(); }, 4 );
		}
		#endregion

		#region LeafSurfaces
		protected IEnumerable<int> LoadLeafSurfaces()
		{
			return ParseLump<int>( Lumps.LeafSurfaces, delegate( BinaryReader reader ) { return reader.ReadInt32(); }, 4 );
		}
		#endregion

		#region DBrush
		protected struct DBrush
		{
			public int FirstSide, NumSides;
			public int ShaderNum;

			internal const int Size = 12;

			internal static DBrush Load( BinaryReader reader )
			{
				DBrush ret;

				ret.FirstSide = reader.ReadInt32();
				ret.NumSides = reader.ReadInt32();
				ret.ShaderNum = reader.ReadInt32();

				return ret;
			}
		}

		protected IEnumerable<DBrush> LoadDBrushes()
		{
			return ParseLump<DBrush>( Lumps.Brushes, DBrush.Load, DBrush.Size );
		}
		#endregion

		#region DBrushSide
		protected struct DBrushSide
		{
			public int PlaneNumber;
			public int ShaderNum;

			internal const int Size = 8;

			internal static DBrushSide Load( BinaryReader reader )
			{
				DBrushSide ret;

				ret.PlaneNumber = reader.ReadInt32();
				ret.ShaderNum = reader.ReadInt32();

				return ret;
			}
		}

		protected IEnumerable<DBrushSide> LoadDBrushSides()
		{
			return ParseLump<DBrushSide>( Lumps.BrushSides, DBrushSide.Load, DBrushSide.Size );
		}
		#endregion
	}
}